"""
Fortran dynamic call detection for CodeMCP framework.

This module detects and analyzes dynamic procedure calls including
procedure pointers and polymorphic calls.
"""

import logging
from typing import Dict, List, Any, Set, Optional
from collections import defaultdict

from ...core.error_handler import AnalysisError, log_info


class DynamicCallInfo:
    """Information about a dynamic procedure call."""
    
    def __init__(self, call_site: str, line_number: int = 0):
        self.call_site = call_site
        self.line_number = line_number
        self.call_type = 'unknown'  # 'procedure_pointer', 'type_bound', 'polymorphic'
        self.possible_targets = set()  # Set of possible target procedures
        self.confidence = 0.0  # Confidence in target resolution (0.0 to 1.0)
        
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            'call_site': self.call_site,
            'line_number': self.line_number,
            'call_type': self.call_type,
            'possible_targets': list(self.possible_targets),
            'confidence': self.confidence
        }


class DynamicCallDetector:
    """
    Fortran dynamic call detector.
    
    Detects and analyzes dynamic procedure calls that cannot be
    resolved statically, including procedure pointers and
    polymorphic type-bound procedure calls.
    """
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self.dynamic_calls = []  # List of DynamicCallInfo
        self.procedure_pointers = {}  # name -> pointer info
        self.polymorphic_variables = {}  # name -> polymorphic info
        self.type_bound_procedures = defaultdict(list)  # type -> [procedures]
    
    def detect_dynamic_calls(self, parsed_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Detect dynamic calls in parsed Fortran data.
        
        Args:
            parsed_data: Parsed Fortran file data
            
        Returns:
            Dynamic call analysis results
        """
        try:
            filename = parsed_data.get('filename', 'unknown')
            log_info(f"Detecting dynamic calls in {filename}")
            
            # Analyze procedure pointers
            self._analyze_procedure_pointers(parsed_data)
            
            # Analyze polymorphic variables
            self._analyze_polymorphic_variables(parsed_data)
            
            # Detect dynamic calls
            self._detect_calls(parsed_data)
            
            # Resolve possible targets
            self._resolve_targets(parsed_data)
            
            result = {
                'filename': filename,
                'dynamic_calls': [call.to_dict() for call in self.dynamic_calls],
                'procedure_pointers': self.procedure_pointers,
                'polymorphic_variables': self.polymorphic_variables,
                'type_bound_procedures': dict(self.type_bound_procedures),
                'statistics': self._compute_statistics()
            }
            
            log_info(f"Dynamic call detection complete for {filename}: "
                    f"{len(self.dynamic_calls)} dynamic calls found")
            
            return result
            
        except Exception as e:
            self.logger.error(f"Error detecting dynamic calls: {e}")
            raise AnalysisError(f"Dynamic call detection failed: {e}")
    
    def _analyze_procedure_pointers(self, parsed_data: Dict[str, Any]):
        """Analyze procedure pointer declarations."""
        # This would need sophisticated parsing to identify procedure pointers
        # For now, we'll do simple pattern matching
        
        for var in parsed_data.get('variables', []):
            var_name = var.get('name', 'unknown')
            var_type = var.get('type', 'unknown')
            
            # Look for POINTER attribute with PROCEDURE
            if 'pointer' in var_type.lower() and 'procedure' in var_type.lower():
                self.procedure_pointers[var_name] = {
                    'name': var_name,
                    'interface': 'unknown',  # Would need more parsing
                    'line_number': var.get('line_number', 0)
                }
    
    def _analyze_polymorphic_variables(self, parsed_data: Dict[str, Any]):
        """Analyze polymorphic variable declarations."""
        # This would need sophisticated parsing to identify CLASS variables
        
        for var in parsed_data.get('variables', []):
            var_name = var.get('name', 'unknown')
            var_type = var.get('type', 'unknown')
            
            # Look for CLASS declarations
            if var_type.lower().startswith('class('):
                # Extract type from CLASS(type_name)
                type_start = var_type.find('(') + 1
                type_end = var_type.find(')', type_start)
                if type_end > type_start:
                    base_type = var_type[type_start:type_end].strip()
                    
                    self.polymorphic_variables[var_name] = {
                        'name': var_name,
                        'base_type': base_type,
                        'line_number': var.get('line_number', 0)
                    }
    
    def _detect_calls(self, parsed_data: Dict[str, Any]):
        """Detect potential dynamic calls."""
        # Analyze calls to see which might be dynamic
        
        for call in parsed_data.get('calls', []):
            target = call.get('target', 'unknown')
            line_number = call.get('line_number', 0)
            
            # Check if this is a call through a procedure pointer
            if target in self.procedure_pointers:
                dynamic_call = DynamicCallInfo(target, line_number)
                dynamic_call.call_type = 'procedure_pointer'
                dynamic_call.confidence = 0.3  # Low confidence without more analysis
                self.dynamic_calls.append(dynamic_call)
            
            # Check for type-bound procedure calls (contains %)
            elif '%' in target:
                dynamic_call = DynamicCallInfo(target, line_number)
                dynamic_call.call_type = 'type_bound'
                dynamic_call.confidence = 0.5  # Medium confidence
                self.dynamic_calls.append(dynamic_call)
    
    def _resolve_targets(self, parsed_data: Dict[str, Any]):
        """Attempt to resolve possible targets for dynamic calls."""
        # Collect all available procedures
        all_procedures = set()
        
        for func in parsed_data.get('functions', []):
            all_procedures.add(func['name'])
        
        for sub in parsed_data.get('subroutines', []):
            all_procedures.add(sub['name'])
        
        # Try to resolve targets for each dynamic call
        for dynamic_call in self.dynamic_calls:
            if dynamic_call.call_type == 'procedure_pointer':
                # For procedure pointers, we'd need interface analysis
                # For now, assume any procedure could be a target
                dynamic_call.possible_targets = all_procedures.copy()
                dynamic_call.confidence = 0.1  # Very low confidence
            
            elif dynamic_call.call_type == 'type_bound':
                # For type-bound procedures, extract the procedure name
                if '%' in dynamic_call.call_site:
                    proc_name = dynamic_call.call_site.split('%')[-1]
                    
                    # Find procedures with this name
                    matching_procedures = {proc for proc in all_procedures 
                                         if proc == proc_name}
                    dynamic_call.possible_targets = matching_procedures
                    
                    if matching_procedures:
                        dynamic_call.confidence = 0.7  # Higher confidence if we find matches
                    else:
                        dynamic_call.confidence = 0.2  # Low confidence if no matches
    
    def _compute_statistics(self) -> Dict[str, Any]:
        """Compute dynamic call detection statistics."""
        total_dynamic_calls = len(self.dynamic_calls)
        
        call_types = defaultdict(int)
        confidence_ranges = {'high': 0, 'medium': 0, 'low': 0}
        
        for call in self.dynamic_calls:
            call_types[call.call_type] += 1
            
            if call.confidence >= 0.7:
                confidence_ranges['high'] += 1
            elif call.confidence >= 0.4:
                confidence_ranges['medium'] += 1
            else:
                confidence_ranges['low'] += 1
        
        return {
            'total_dynamic_calls': total_dynamic_calls,
            'procedure_pointer_calls': call_types['procedure_pointer'],
            'type_bound_calls': call_types['type_bound'],
            'polymorphic_calls': call_types['polymorphic'],
            'high_confidence_calls': confidence_ranges['high'],
            'medium_confidence_calls': confidence_ranges['medium'],
            'low_confidence_calls': confidence_ranges['low'],
            'total_procedure_pointers': len(self.procedure_pointers),
            'total_polymorphic_variables': len(self.polymorphic_variables)
        }
    
    def get_call_targets(self, call_site: str) -> List[str]:
        """
        Get possible targets for a specific call site.
        
        Args:
            call_site: The call site to analyze
            
        Returns:
            List of possible target procedure names
        """
        for call in self.dynamic_calls:
            if call.call_site == call_site:
                return list(call.possible_targets)
        return []
    
    def is_dynamic_call(self, call_site: str) -> bool:
        """
        Check if a call site is identified as dynamic.
        
        Args:
            call_site: The call site to check
            
        Returns:
            True if the call is dynamic
        """
        return any(call.call_site == call_site for call in self.dynamic_calls)
    
    def get_call_confidence(self, call_site: str) -> float:
        """
        Get confidence level for target resolution of a call site.
        
        Args:
            call_site: The call site to check
            
        Returns:
            Confidence level (0.0 to 1.0)
        """
        for call in self.dynamic_calls:
            if call.call_site == call_site:
                return call.confidence
        return 1.0  # Static calls have full confidence